Python Paddlepaddle打docker镜像包发布服务

环境准备

linux 环境
docker 环境
docker-compose 环境
python3.7

打包准备

dockerfile 文件夹下有四个文件,将相应文件放到linux 的dockerfile文件夹下

1
2
3
4
5
6
dockerfile 
├─Dockerfile #为打包使用的Dockerfile的文件
├─flask_model_zcy.py #为启动的主函数的文件
├─paddlezcy.yml #为docker-compose 的启动文件 打包之后启动使用
├─requirements.txt #为python 的相关依赖
└─save_dir_final.pdparams #为训练出的模型文件
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#基于的基础镜像
FROM python:3.7
#解决 ImportError: libGL.so.1: cannot open shared object file: No such file or directory 问题
#RUN (apt-get update) && (apt-get install -y libgl1-mesa-dev ffmpeg libsm6 libxext6)
#ENV PYTHONUNBUFFERED 1

RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
RUN apt-get clean
RUN apt-get update
RUN apt-get install ffmpeg libsm6 libxext6 -y

#设置上海时间
ENV TZ=Asia/Shanghai
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
#中文乱码en_US.utf8
ENV LANG en_US.utf8


#代码添加到code文件夹
ADD . /code
# 设置code文件夹是工作目录
WORKDIR /code
# 安装支持
#RUN pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
RUN pip install --no-index --find-links=py_pkgs -r requirements.txt
# 暴露对外端口
EXPOSE 5000
# 启动命令
CMD ["python", "/code/flask_model_zcy.py"]
flask_model_zcy.py

注意 model__state_dict = paddle.load(‘./save_dir_final.pdparams’) 此处模型位置写正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
import io
from flask import Flask, jsonify, request, abort
import json
import base64
import numpy as np

import os
from PIL import Image
import cv2
from io import BytesIO, StringIO

# 引入需要的模块
import os
import zipfile
import random
import json
import paddle
import sys
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from paddle.io import Dataset

app = Flask(__name__)

#所有的标签,以及数字对应的标签
label_dic = {'0': 'baihe', '1': 'dangshen', '2': 'gouqi', '3': 'huaihua', '4': 'jinyinhua'}
#所有的标签数量
class_dim = 0
for key in label_dic:
class_dim =class_dim+1
print(class_dim)



@app.route('/zcy', methods=['GET', 'POST'])
def call_analysis():
print("Hello, World!")

data1 = request.data #----获取的是字符串
#print(data1)
data2 = request.get_data() #----获取的是字符串
#print(data2)
j_data = json.loads(data2) #-----load将字符串解析成json
# print(j_data)
# print(j_data['base64'])

# decode_base64_matplot_img(j_data['base64'])
# return ""
return getAnalysis(j_data['base64'])


def getAnalysis(imgbase64):



# 加载训练过程保存的最后一个模型
model__state_dict = paddle.load('./save_dir_final.pdparams')
model_predict = VGGNet()
model_predict.set_state_dict(model__state_dict)
model_predict.eval()

infer_img = load_image2(imgbase64)
infer_img = infer_img[np.newaxis,:, : ,:] #reshape(-1,3,224,224)
infer_img = paddle.to_tensor(infer_img)
result = model_predict(infer_img)
print(result.numpy())
lab = np.argmax(result.numpy())
print("样本: {},被预测为:{}".format("1",label_dic[str(lab)]))
return "样本被预测为:{}".format(label_dic[str(lab)])

# def getbase64(imgbase64):


# # 传入为RGB格式下的base64,传出为RGB格式的numpy矩阵
# byte_data = base64.b64decode(imgbase64)#将base64转换为二进制
# encode_image = np.asarray(bytearray(byte_data), dtype="uint8")# 二进制转换为一维数组
# img_array = cv2.imdecode(encode_image, cv2.IMREAD_COLOR)# 用cv2解码为三通道矩阵
# img_array = cv2.cvtColor(img_array, cv2.COLOR_BGR2RGB)# BGR2RGB

# img1 = img_array
# imgs = [img1]
# #img2 = cv2.imread('./data/images/zidane.jpg')[:,:,::-1]
# #imgs = [img2]

# results = model(imgs, size=640)
# print('识别成功')
# results.ims
# results.render()
# base64_image = ''
# for img in results.ims:
# buffered = BytesIO()
# img_base64 = Image.fromarray(img)
# img_base64.save(buffered, format='JPEG')
# base64_image = base64.b64encode(buffered.getvalue()).decode('utf-8')
# # with open('./result.txt', 'a+') as f:
# # f.write(base64_image)
# # f.close()
# print(results)
# results.save()
# base64_image = 'data:image/jpeg;base64,%s' % base64_image
# return base64_image




# 定义卷积池化网络
class ConvPool(paddle.nn.Layer):
'''卷积+池化'''
def __init__(self,
num_channels,
num_filters,
filter_size,
pool_size,
pool_stride,
groups,
conv_stride=1,
conv_padding=1,
):
super(ConvPool, self).__init__()

# groups代表卷积层的数量
for i in range(groups):
self.add_sublayer( #添加子层实例
'bb_%d' % i,
paddle.nn.Conv2D( # layer
in_channels=num_channels, #通道数
out_channels=num_filters, #卷积核个数
kernel_size=filter_size, #卷积核大小
stride=conv_stride, #步长
padding = conv_padding, #padding
)
)
self.add_sublayer(
'relu%d' % i,
paddle.nn.ReLU()
)
num_channels = num_filters


self.add_sublayer(
'Maxpool',
paddle.nn.MaxPool2D(
kernel_size=pool_size, #池化核大小
stride=pool_stride #池化步长
)
)

def forward(self, inputs):
x = inputs
for prefix, sub_layer in self.named_children():
# print(prefix,sub_layer)
x = sub_layer(x)
return x

# VGG网络
class VGGNet(paddle.nn.Layer):
def __init__(self):
super(VGGNet, self).__init__()
# 5个卷积池化操作
self.convpool01 = ConvPool(
3, 64, 3, 2, 2, 2) #3:通道数,64:卷积核个数,3:卷积核大小,2:池化核大小,2:池化步长,2:连续卷积个数
self.convpool02 = ConvPool(
64, 128, 3, 2, 2, 2)
self.convpool03 = ConvPool(
128, 256, 3, 2, 2, 3)
self.convpool04 = ConvPool(
256, 512, 3, 2, 2, 3)
self.convpool05 = ConvPool(
512, 512, 3, 2, 2, 3)
self.pool_5_shape = 512 * 7* 7
# 三个全连接层
self.fc01 = paddle.nn.Linear(self.pool_5_shape, 4096)
self.drop1 = paddle.nn.Dropout(p=0.5)
self.fc02 = paddle.nn.Linear(4096, 4096)
self.drop2 = paddle.nn.Dropout(p=0.5)
self.fc03 = paddle.nn.Linear(4096, class_dim)

def forward(self, inputs, label=None):
# print('input_shape:', inputs.shape) #[8, 3, 224, 224]
"""前向计算"""
out = self.convpool01(inputs)
# print('convpool01_shape:', out.shape) #[8, 64, 112, 112]
out = self.convpool02(out)
# print('convpool02_shape:', out.shape) #[8, 128, 56, 56]
out = self.convpool03(out)
# print('convpool03_shape:', out.shape) #[8, 256, 28, 28]
out = self.convpool04(out)
# print('convpool04_shape:', out.shape) #[8, 512, 14, 14]
out = self.convpool05(out)
# print('convpool05_shape:', out.shape) #[8, 512, 7, 7]

out = paddle.reshape(out, shape=[-1, 512*7*7])
out = self.fc01(out)
out = self.drop1(out)
out = self.fc02(out)
out = self.drop2(out)
out = self.fc03(out)

if label is not None:
acc = paddle.metric.accuracy(input=out, label=label)
return out, acc
else:
return out





def load_image(img_path):
'''
预测图片预处理
'''
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img = img.resize((224, 224), Image.BILINEAR)
img = np.array(img).astype('float32')
img = img.transpose((2, 0, 1)) / 255 # HWC to CHW 及归一化
return img

def load_image2(img_base64):


'''
预测图片预处理
'''
# byte_data = base64.b64decode(img_base64)#将base64转换为二进制
# img = np.fromstring(byte_data, np.uint8) # 转换np序列



# img = np.asarray(bytearray(byte_data), dtype="uint8")# 二进制转换为一维数组
# img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

# plt.imshow(img)
# plt.axis('off')
# plt.show()
# sys.stdout.flush()

img = base64.b64decode(img_base64)
buffer = io.BytesIO()
img = Image.open(io.BytesIO(img))
# img = Image.open(img_base64)
if img.mode != 'RGB':
img = img.convert('RGB')
img = img.resize((224, 224), Image.BILINEAR)
img = np.array(img).astype('float32')
print('numpy: ', img.shape)
img = img.transpose((2, 0, 1)) / 255 # HWC to CHW 及归一化
return img

# 解码base64字符串为matplot图像
def decode_base64_matplot_img(base64_data):
img = base64.b64decode(base64_data)
img_array = np.fromstring(img, np.uint8) # 转换np序列
img_raw = cv2.imdecode(img_array, cv2.IMREAD_COLOR) # 转换Opencv格式BGR
img_matplot = cv2.cvtColor(img_raw, cv2.COLOR_BGR2RGB) # BGR转RGB

img_gray = cv2.imdecode(img_array, cv2.IMREAD_GRAYSCALE) # 转换灰度图
imggray_matplot = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2RGB) # 灰度图转RGB
plt.figure()
plt.title("Matplot RGB Origin Image")
plt.axis("off")
plt.imshow(img_matplot)

plt.figure()
plt.title("Matplot Gray Origin Image")
plt.axis("off")
plt.imshow(imggray_matplot)
plt.show()

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug = False)

#json模块编码: json.dumps()
#json模块解码:解码python json格式,用json.loads()
paddlezcy.yml
1
2
3
4
5
6
7
8
9
10
#docker-compose -f paddlezcy.yml up -d
#paddlezcy.yml 配置文件如下
version: "3"
services:
paddlezcy:
image: paddlezcy:v1
container_name: paddlezcy
privileged: true
ports:
- 5001:5000
requirements.txt

可由 pip freeze > requirements.txt 命令生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
astor==0.8.1
certifi==2022.12.7
charset-normalizer==3.1.0
click==8.1.3
colorama==0.4.6
cycler==0.11.0
decorator==5.1.1
Flask==2.2.5
fonttools==4.38.0
idna==3.4
importlib-metadata==6.6.0
itsdangerous==2.1.2
Jinja2==3.1.2
kiwisolver==1.4.4
MarkupSafe==2.1.2
matplotlib==3.5.3
numpy==1.21.6
opencv-python==4.3.0.38
opt-einsum==3.3.0
packaging==23.1
paddle-bfloat==0.1.7
paddlepaddle==2.4.2
Pillow==9.5.0
protobuf==3.20.0
pyparsing==3.0.9
python-dateutil==2.8.2
requests==2.29.0
six==1.16.0
torch==1.13.1
typing_extensions==4.5.0
urllib3==1.26.15
Werkzeug==2.2.3
zipp==3.15.0

开始打包

下载依赖到本地

1
pip download -d py_pkgs -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

注意需要在linux 下执行,因打包docker 的基础镜像也为linux的系统
依赖比较多下载时间比较长,建议在网络环境较好的情况下进行

执行之后生成 py_pkgs 文件夹,内为需要的python 依赖包

执行打包命令

1
docker build -t paddlezcy:v1 .

docker 打包命令注意不要漏掉命令的最后“.”
打包时间比较长,建议在网络环境较好的情况进行

查看镜像包

1
2
(paddle) [root@localhost dockerfile]# docker images  
paddlezcy v1 228d20fd403d 1 hours ago 7.96GB

启动服务

使用docker-compose启动服务

1
docker-compose -f paddlezcy.yml up -d

查看服务

1
2
3
(paddle) [root@localhost dockerfile]# docker ps 
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
741fba797df6 paddlezcy:v1 "python /code/flask_…" 13 hours ago Up About an hour 0.0.0.0:5001->5000/tcp, :::5001->5000/tcp paddlezcy

问题解决

注意,这些问题在服务启动,或则调用的时候出现,需要在打包的时候进行处理,
docker logs -f 服务id 进行查看日志

问题1
1
2
3
4
5
6
7
8
9
10
C++ Traceback (most recent call last):
--------------------------------------
No stack trace in paddle, may be caused by external reasons.

----------------------
Error Message Summary:
----------------------
FatalError: `Segmentation fault` is detected by the operating system.
[TimeInfo: *** Aborted at 1684242600 (unix time) try "date -d @1684242600" if you are using GNU date ***]
[SignalInfo: *** SIGSEGV (@0x10) received by PID 1 (TID 0x7f26a3395700) from PID 16 ***]
解决方式

1.降低 opencv-python版本,从4.4版本降低到4.2版本,

1
sudo pip install --upgrade opencv-python==4.2.0.32

此处把requirements.txt 中的依赖调低
若opencv-python 调低之后显示无版本,则按照提示版本进行调整

2.通常很难出现这样的错误。只能一点点排除:
1.磁盘空间满了。 比如/tmp, /var 或者是/分区满了。
2.文件读写错误,在临时目录里,某些文件被锁,无法读写导致
3.内存不足(这个可能性小),你可以将占用内存多的程序去掉
4.你是在虚拟机里运行,可能内存访问函数不能正确使用
5.有防火墙的问题
6.可能是权限的问题,比如某些程序需要超级用户的权限
7.程序本身有BUG,它预留的计算空间不够。你可以检查一下配置什么的,看看有没有设置预加载内存的配置。

内存不足,本次运行的时候正好遇到,建议将多余的程序进行关闭

问题2
1
2
from .cv2 import *
ImportError: libGL.so.1: cannot open shared object file: No such file or directory
解决方式

ubuntu 系统

1
2
3
4
5
apt install libgl1-mesa-glx
```
Centos,yum没有找到这个安装包

通过yum list | grep libgl 发现有两个包相关性比较强

mesa-libglapi.x86_64 18.3.4-12.el7_9
pygtk2-libglade.x86_64 2.24.0-9.el7

1
进行安装

yum install mesa-libglapi.x86_64 pygtk2-libglade.x86_64

1
2
3
4

## 验证服务

外部使用postman调用该地址,注意将ip 换成启动服务的机器地址

http://192.168.23.134:5001/zcy

1
调用参数

{
“base64”:”/9j/2wCEA…..
}

1
2
3
4
5
base64 传入的为图片的base64位编码,注意将data:image/jpeg;base64,前缀去掉

可用于图片转换base64的网站 https://c.runoob.com/front-end/59/

调用返回

样本被预测为:gouqi

```
则验证服务调用成功

一辈子很短,努力的做好两件事就好;
第一件事是热爱生活,好好的去爱身边的人;
第二件事是努力学习,在工作中取得不一样的成绩,实现自己的价值,而不是仅仅为了赚钱;

继开 wechat
欢迎加我的微信,共同交流技术